let pi = 3.14159265

(*drawoObj affiche le tableau de faces et le tableau de normales*)
let drawObj fline fArray nArray =
	let is_clockwise (nx,ny,nz) =
		(ny <= 0.)
	in
	let draw n (p1,p2,p3) =
		  GlDraw.normal3 n;
                  GlDraw.vertex3 p1;
                  GlDraw.normal3 n;
                  GlDraw.vertex3 p2;
                  GlDraw.normal3 n;
                  GlDraw.vertex3 p3;
	in

        begin
                for i = 0 to ((Array.length fArray) -1) do
                    let (v1,v2,v3) = fArray.(i) in
                      if fline v1 then
                        GlDraw.polygon_mode `both `line
                      else
                        GlDraw.polygon_mode `both `fill;
                      GlDraw.begins `triangles;
                      let n = nArray.(i) in
                        if is_clockwise n then
                          draw n (v1,v2,v3)
                        else
                          draw n (v3,v2,v1);
                        GlDraw.ends ();
                done;
        end

(*getTarget renvoie la position suivante de la camera selon
un deplacement vers l'avant *)
let getTarget pos dir =
	(pos.Geometry.x +. dir.Geometry.x,
       	pos.Geometry.y +. dir.Geometry.y,
	pos.Geometry.z +. dir.Geometry.z)

(* fix permet de remettre le vecteur sherique dans les bornes *)
let fix dir =
        if dir.Geometry.x < -.pi then
           dir.Geometry.x <- -.pi
        else if dir.Geometry.y < -.pi*. 2. then
               dir.Geometry.y <- 0.
        else if dir.Geometry.x > pi then
		dir.Geometry.x <- pi
	else if dir.Geometry.y > (pi*. 2.) then
		dir.Geometry.y <- 0.
	else
		()

(*transforme le vecteur sherique dir en un triplets
de coordonnees cartesienns *)
let toCart dir =
	fix dir;
	let (x,y,z) = (
        10.1*. (sin dir.Geometry.x) *.(cos dir.Geometry.y),
        10.1 *. (sin dir.Geometry.x) *.(sin dir.Geometry.y),
        10.1 *. (cos dir.Geometry.x) ) in
	{Geometry.x = x; Geometry.y = z; Geometry.z = y}


(*display est la fonction principale de l'affichage, elle prend en parametre
le boolen de position de la lumier (camera ou global), la taille de lecran,
le tableaux des faces a afficher, et le tableau des normales, la direction
et la position de la camera*)
let display fline light_state w h fArray nArray dir pos () =
    GlClear.clear [`color; `depth];
    Gl.enable `depth_test;
    GlMat.mode `projection;
    GlMat.load_identity ();

    let aspect = float w /. float h in
    GluMat.perspective ~fovy:90.0 ~aspect:aspect ~z:(0.1, 10000.);

    let vect = toCart dir in
    GluMat.look_at ~eye:(pos.Geometry.x, pos.Geometry.y, pos.Geometry.z)
			~center:(getTarget pos vect) ~up:(0., 1., 0.);
    GlMat.mode `modelview;
    GlMat.load_identity ();

    Gl.enable `lighting;
    Gl.enable `light0;

    drawObj fline fArray nArray;
    GlDraw.begins `lines;
    GlDraw.color (1.0 ,0.,0.);
    GlDraw.vertex3 (0.,0.,0.);
    GlDraw.color (1.0 ,0.,0.);
    GlDraw.vertex3 (0.,2000.,0.);
    GlDraw.color (0.0 ,0.,1.);
    GlDraw.vertex3 (0.,0.,0.);
    GlDraw.color (0.0 ,0.,1.);
    GlDraw.vertex3 (0.,0.,2000.);
    GlDraw.color (0.0 ,1.,0.);
    GlDraw.vertex3 (0.,0.,0.);
    GlDraw.color (0.0 ,1.,0.);
    GlDraw.vertex3 (2000.,0.,0.);
    GlDraw.ends ();
    GlDraw.color (1.,1.,1.);

    if !light_state then
 	 GlMat.translate3 (Geometry.toC3 pos)
    else
    	 GlMat.translate3 (200.,2000.,200.);

    GlLight.material `both (`shininess 100.);
    Gl.enable `color_material;
    GlLight.light ~num:0 (`position (0.,0.0,0.,20.));
    GlLight.color_material `both `specular;
    GlLight.color_material `both `ambient_and_diffuse;
    Gl.flush ()



(*idle est la fonction qui est appelle a chaque tour de boucle *)
let idle wire w h fArray nArray dir pos =
    display wire w h fArray nArray dir pos

(*reshape est appelle au debut de la boucle Glut et permet d'adapter la
taille de la fenetre au rendu *)
let reshape ~w ~h =
  GlDraw.viewport ~x:0 ~y:0 ~w:w ~h:h;
  GlMat.mode `projection;
  GlMat.load_identity ();
  GlMat.ortho ~x:(-2.5,2.5) ~z:(-10.0,10.0)
              ~y:(-2.5 *. float h /. float w, 2.5 *. float h /. float w);
  GlMat.mode `modelview;
  GlMat.load_identity ()

(*cette fonction deplace pos de dir selon l'operation op *)
let forward dir pos op =
	pos.Geometry.x <- op pos.Geometry.x (dir.Geometry.x);
	pos.Geometry.y <- op pos.Geometry.y (dir.Geometry.y);
	pos.Geometry.z <- op pos.Geometry.z (dir.Geometry.z)

(*cette fonction deplace le vecteur pos selon le vecteur perpendiculaire
et de meme norme que dir *)
let sideways dir pos op =
        pos.Geometry.x <- op pos.Geometry.x dir.Geometry.z;
        pos.Geometry.y <- op pos.Geometry.y dir.Geometry.x;
        pos.Geometry.z <- op pos.Geometry.z dir.Geometry.y

(* key_callback gere les evene;emts speciaux du clavier comme
l'appuie sur les fleche directionnelles ou les F1-F12, elle prend
la direction et la position de la camera en parametre*)
let key_callback dir pos ~key =
        let redis = ref true in
       		match key with
            		| 116  -> forward (toCart dir) pos (-.) 
            		| 111    -> forward (toCart dir) pos (+.) 
            		| _ -> (redis := false)

(* keyboard gere les evenements du clavier elle prend le boolen fillaire
en parametre, la position de la camera et la direction de la camera *)
let keyboard light_state wire pos dir ~key ~x ~y =
	match key with
		 100 -> forward {Geometry.x = 0.; Geometry.y = pi/.80.;
				 Geometry.z = 0.} dir  (-.)
		|97-> forward {Geometry.x = 0.; Geometry.y = -. pi/.80.;
				Geometry.z = 0.} dir  (-.)
		|111-> forward {Geometry.x = pi/.80.; Geometry.y = 0.;
				Geometry.z = 0.} dir  (-.)
                |116-> forward {Geometry.x = -. pi/.80.; Geometry.y = 0.;
				Geometry.z = 0.} dir  (-.)
		|122 -> (wire := not !wire ;
			if !wire then
                        	GlDraw.polygon_mode `both `line
			else
				GlDraw.polygon_mode `both `fill)
		|27 -> exit (0)
		| 102 -> light_state := not !light_state

		|_ -> ()

(*mouse permet de recuperer et de mettre a jour le position de la souris
et la direction de la camera, on lappelle avec les reference de la
position du curseur, les demies largeur et longueur de l'ecran et
le vecteur direction*)

let mouse mx my w h dir ~x ~y =

		dir.Geometry.y <- dir.Geometry.y -.
		(float_of_int (!mx - x + w))*.pi/.1500.;
                dir.Geometry.x <- dir.Geometry.x -.
		(float_of_int (!my - y + h))*.pi/.1500.;
		mx:= x - w;
		my := y - h

(* updateMouse permet de garder en memoire la position de la souris *)
let updateMouse mx my w h ~x ~y =
	 mx:= x - w;
         my := y - h


(* getNarray prend en parametre un tableau de triplets de triplets
de flotants corresondant aux points des triangles et renvoie le tableau
des normale de ces faces sous forme *)
let getNarray fArray =
	let nArray = Array.make (Array.length fArray) (0.,0.,0.) in
	for i = 0 to (Array.length fArray) - 1 do
		let (v1,v2,v3) = fArray.(i) in
		let a = Geometry.fromC3 v3 in
		let b = Geometry.fromC3 v2 in
		let c = Geometry.fromC3 v1 in
		let n =  Geometry.cross (Geometry.diff a b)
                                       (Geometry.diff a c) in
                Geometry.normalize n;
		nArray.(i) <- Geometry.toC3 n;
	done;
	nArray

(* execute est appelee de l'exterieur avec une chaine de caractere
et permet l'ouverture de la fenetre et l'affichage du fichier obj
dont le nom est la chaine de caractere *)
